package com.security.sm2;

import cn.hutool.core.util.HexUtil;
import org.bouncycastle.asn1.gm.GMNamedCurves;
import org.bouncycastle.asn1.x9.X9ECParameters;
import org.bouncycastle.crypto.CryptoException;
import org.bouncycastle.crypto.InvalidCipherTextException;
import org.bouncycastle.crypto.engines.SM2Engine;
import org.bouncycastle.crypto.params.*;
import org.bouncycastle.crypto.signers.SM2Signer;
import org.bouncycastle.jcajce.provider.asymmetric.ec.BCECPrivateKey;
import org.bouncycastle.jcajce.provider.asymmetric.ec.BCECPublicKey;
import org.bouncycastle.jce.provider.BouncyCastleProvider;
import org.bouncycastle.math.ec.ECCurve;
import org.bouncycastle.math.ec.ECPoint;
import org.bouncycastle.math.ec.custom.gm.SM2P256V1Curve;
import org.bouncycastle.util.Strings;
import org.bouncycastle.util.encoders.Hex;

import java.io.IOException;
import java.math.BigInteger;
import java.nio.charset.Charset;
import java.security.*;
import java.security.spec.ECFieldFp;
import java.security.spec.ECGenParameterSpec;
import java.security.spec.EllipticCurve;
import java.util.Base64;

/**
 * SM2 工具类
 */
public class SM2Util {

    static {
        Security.addProvider(new BouncyCastleProvider());
    }

    /*
     * 以下为SM2推荐曲线参数
     */
    public static final SM2P256V1Curve CURVE = new SM2P256V1Curve();
    public final static BigInteger SM2_ECC_P = CURVE.getQ();
    public final static BigInteger SM2_ECC_A = CURVE.getA().toBigInteger();
    public final static BigInteger SM2_ECC_B = CURVE.getB().toBigInteger();
    public final static BigInteger SM2_ECC_N = CURVE.getOrder();
    public final static BigInteger SM2_ECC_H = CURVE.getCofactor();
    public final static BigInteger SM2_ECC_GX = new BigInteger(
            "32C4AE2C1F1981195F9904466A39C9948FE30BBFF2660BE1715A4589334C74C7", 16);
    public final static BigInteger SM2_ECC_GY = new BigInteger(
            "BC3736A2F4F6779C59BDCEE36B692153D0A9877CC62A474002DF32E52139F0A0", 16);
    public static final ECPoint G_POINT = CURVE.createPoint(SM2_ECC_GX, SM2_ECC_GY);
    public static final ECDomainParameters DOMAIN_PARAMS = new ECDomainParameters(CURVE, G_POINT,
            SM2_ECC_N, SM2_ECC_H);
    public static final int CURVE_LEN = BCECUtil.getCurveLength(DOMAIN_PARAMS);
    //

    public static final EllipticCurve JDK_CURVE = new EllipticCurve(new ECFieldFp(SM2_ECC_P), SM2_ECC_A, SM2_ECC_B);
    public static final java.security.spec.ECPoint JDK_G_POINT = new java.security.spec.ECPoint(
            G_POINT.getAffineXCoord().toBigInteger(), G_POINT.getAffineYCoord().toBigInteger());
    public static final java.security.spec.ECParameterSpec JDK_EC_SPEC = new java.security.spec.ECParameterSpec(
            JDK_CURVE, JDK_G_POINT, SM2_ECC_N, SM2_ECC_H.intValue());



    // SM2推荐曲线名称
    public static final String SM2_CURVE_NAME = "sm2p256v1";

    public static final Charset UTF_8 = Charset.forName("utf-8");

    //openssl生成sm2公私钥对
    //公钥
//    public static String pu1 = "MFkwEwYHKoZIzj0CAQYIKoEcz1UBgi0DQgAESxNii7P1V11M5BhUKEdtLjS956JY"
//            + "PVHjppNMFxbtMFkOaYSz+yrxGxSg/SkRyHOkexh6Ujmx/zxPcZy5XtMTLg==";

    //私钥
//    public static String pr1 = "MIGTAgEAMBMGByqGSM49AgEGCCqBHM9VAYItBHkwdwIBAQQgSFzGq0mvWZuUeh9Y"
//            +"DyTJFp7JsjOzkbGgNwm8DM8c/A2gCgYIKoEcz1UBgi2hRANCAARLE2KLs/VXXUzk"
//            +"GFQoR20uNL3nolg9UeOmk0wXFu0wWQ5phLP7KvEbFKD9KRHIc6R7GHpSObH/PE9x"
//            +"nLle0xMu";

    //openssl生成sm2公私钥对
    //公钥 x509格式
    public static String pu1 = "MFkwEwYHKoZIzj0CAQYIKoEcz1UBgi0DQgAEo0Ih0yAevaw//5anG7FHwp2EBSkv"
            + "Q6pbPvgPRjaEps7s3teQh+UtrPjY/DAUQ84u4oO4oBYpsEQ5J/RdD+t/bQ==";

    //私钥 PKCS8格式
//    public static String pr1 = "MIGHAgEAMBMGByqGSM49AgEGCCqBHM9VAYItBG0wawIBAQQgEMw+P2lEjg9XtFMD"
//            + "9FxHFd571h+mJZ5PBkcCsE/EFmShRANCAASjQiHTIB69rD//lqcbsUfCnYQFKS9D"
//            + "qls++A9GNoSmzuze15CH5S2s+Nj8MBRDzi7ig7igFimwRDkn9F0P639t";

    //私钥 PKCS1格式 --> EC
    public static String pr1 = "MHcCAQEEIBDMPj9pRI4PV7RTA/RcRxXee9YfpiWeTwZHArBPxBZkoAoGCCqBHM9V"
            + "AYItoUQDQgAEo0Ih0yAevaw//5anG7FHwp2EBSkvQ6pbPvgPRjaEps7s3teQh+Ut"
            + "rPjY/DAUQ84u4oO4oBYpsEQ5J/RdD+t/bQ==";


    public static void main(String[] args) throws Exception {
        //公钥文件是x509格式，转换成ECPublicKey，提取真正的公钥
        byte[] x509pu1 = Base64.getDecoder().decode(pu1);
        BCECPublicKey bcecPublicKey = BCECUtil.convertX509ToECPublicKey(x509pu1);
        String s1 = HexUtil.encodeHexStr(bcecPublicKey.getQ().getEncoded(false));
        System.out.println("公钥：" + s1);
        byte[] pkcspr1 = Base64.getDecoder().decode(pr1);
        //私钥文件是pkcs8格式，转换成ECPrivateKey，提取真正的私钥
//      BCECPrivateKey bcecPrivateKey = BCECUtil.convertPKCS8ToECPrivateKey(pkcspr1);
        //私钥文件是pkcs1格式，转换成ECPrivateKey，提取真正的私钥
        BCECPrivateKey bcecPrivateKey = BCECUtil.convertSEC1ToBCECPrivateKey(pkcspr1);
        String s2 = HexUtil.encodeHexStr(bcecPrivateKey.getD().toByteArray());
//        java会在私钥前加00，要去除
//        s2 = s2.substring(2);
        System.out.println("私钥：" + s2);
        System.out.println("==========SM2测试加密解密==========");
        //公钥 = 04a34221d3201ebdac3fff96a71bb147c29d8405292f43aa5b3ef80f463684a6ceecded79087e52dacf8d8fc301443ce2ee283b8a01629b0443927f45d0feb7f6d
        //私钥 = 10cc3e3f69448e0f57b45303f45c4715de7bd61fa6259e4f064702b04fc41664
        String publicKey = s1;
        String str = "SM2测试加密解密";
//        String en = encryptBySM2(str, Base64.getEncoder().encodeToString(HexUtil.decodeHex(publicKey)));
//        System.out.println("加密后：" + en);

        //String privateKey = "8a5223d5d597fbdefb8dc40ea070d0aba5ed6d669acdc65d2621e35e9021283e";
        String privateKey = s2;
        String en = "";
        //openssl 加密后
//        en = "MG8CIHAyyaFTeu+9vkLepsya4b4/SLrU+2O8L1zbKiHzDNlkAiB6+wYcu+FbAv/nrMciHZ/o3hRV" +
//                "t+xf9RVYNGITdCsxJAQghW52bUzFV2pgPL/9x8+Xc/dxzq5YwpGa+2Klj/jZrYsEB8ajZW3pTr4=";
        en = "MIHDAiEA6+TJ5hnbJEBrb0sQg8Fj7CRfvn9CCtMZyXxpFHSQS0ICIQCWM2wfsMK/K+v0uZIu22GT" +
                "oXkM+HmPqKag3HydhDSzKwQgbem+ft4u+fWWX+R1M2pLx1YriMOUhfbyPoQTJ2Uqn/gEWdf9Z1SU" +
                "k95KHPdBOhNmdqoGKqxSDbMPL4BPr2yFxD0uW0FGfQksA/uztLMmG2SU85dj1dibwQPYOIEl2Cdf" +
                "6YbO2H539+OeJqBTZVGsnlRY1eLDPpSD3uAP";
//        String de = decryptBySM2(en, Base64.getEncoder().encodeToString(HexUtil.decodeHex(privateKey)));
//        System.out.println("解密后：" + de);
        String jia = SM2Util.encryptBySM2(str,Base64.getEncoder().encodeToString(HexUtil.decodeHex(publicKey)));
        String jie = SM2Util.decryptBySM2(jia,Base64.getEncoder().encodeToString(HexUtil.decodeHex(privateKey)));
        System.out.println("加密后:"+jia);
        System.out.println("解密后:"+jie);

    }

    /**
     * 生成密钥
     *
     * @return
     * @throws Exception
     */
    public static KeyPair genKeyPair() throws Exception {
        final ECGenParameterSpec sm2Spec = new ECGenParameterSpec(SM2_CURVE_NAME);
        // 获取一个椭圆曲线类型的密钥对生成器
        final KeyPairGenerator kpg = KeyPairGenerator.getInstance("EC", new BouncyCastleProvider());
        SecureRandom random = new SecureRandom();
        // 使用SM2的算法区域初始化密钥生成器
        kpg.initialize(sm2Spec, random);
        // 获取密钥对
        KeyPair keyPair = kpg.generateKeyPair();

        return keyPair;
    }

    /**
     * SM2根据公钥加密 param: message 待加密内容 ， publicKey 加密公钥（BASE64编码） return:
     * 加密信息的Base64编码
     *
     * @throws InvalidCipherTextException
     */
    public static String encryptBySM2(String message, String publicKey) throws InvalidCipherTextException, IOException {
        ECDomainParameters domin = getDomain();
        // 公钥对象
        ECPublicKeyParameters pubKeyParameters = getPubKey(publicKey, domin);
        byte[] cipherBytes = new byte[0];
        cipherBytes = encrypt(MySm2Engine.Mode.C1C3C2, pubKeyParameters, message.getBytes(UTF_8));
        return Base64.getEncoder().encodeToString(cipherBytes);
    }

    /**
     * SM2根据私钥解密 param： cipherText 待解密密文 privateKey-私钥（BASE64编码）
     */
    public static String decryptBySM2(String cipherText, String privateKey) throws InvalidCipherTextException, IOException {
        BigInteger d = new BigInteger(1, Base64.getDecoder().decode(privateKey));
        ECDomainParameters domin = getDomain();
        // 私钥对象
        ECPrivateKeyParameters ecPrivateKeyParameters = new ECPrivateKeyParameters(d, domin);
        byte[] decrypt = decrypt(MySm2Engine.Mode.C1C3C2, ecPrivateKeyParameters, Base64.getDecoder().decode(cipherText));
        return new String(decrypt, UTF_8);
    }

    /**
     * 根据公钥字符串创建公钥对象
     *
     */
    public static ECPublicKeyParameters getPubKey(String publicKey, ECDomainParameters domain) {
        ECCurve curve = domain.getCurve();
        ECPoint point = curve.decodePoint(Base64.getDecoder().decode(publicKey));
        ECPublicKeyParameters PublicKey = new ECPublicKeyParameters(point, domain);
        return PublicKey;
    }

    /**
     * @param mode             指定密文结构，旧标准的为C1C2C3，新的[《SM2密码算法使用规范》 GM/T
     *                         0009-2012]标准为C1C3C2
     * @param pubKeyParameters 公钥
     * @param srcData          原文
     * @return 根据mode不同，输出的密文C1C2C3排列顺序不同。C1为65字节第1字节为压缩标识，这里固定为0x04，后面64字节为xy分量各32字节。C3为32字节。C2长度与原文一致。
     * @throws InvalidCipherTextException
     */
    public static byte[] encrypt(MySm2Engine.Mode mode, ECPublicKeyParameters pubKeyParameters, byte[] srcData)
            throws InvalidCipherTextException, IOException {
        MySm2Engine engine = new MySm2Engine(mode);
        ParametersWithRandom pwr = new ParametersWithRandom(pubKeyParameters, new SecureRandom());
        engine.init(true, pwr);
        return engine.processBlock(srcData, 0, srcData.length);
    }

    /**
     * @param mode             指定密文结构，旧标准的为C1C2C3，新的[《SM2密码算法使用规范》 GM/T
     *                         0009-2012]标准为C1C3C2
     * @param priKeyParameters 私钥
     * @param sm2Cipher        根据mode不同，需要输入的密文C1C2C3排列顺序不同。C1为65字节第1字节为压缩标识，这里固定为0x04，后面64字节为xy分量各32字节。C3为32字节。C2长度与原文一致。
     * @return 原文。SM2解密返回了数据则一定是原文，因为SM2自带校验，如果密文被篡改或者密钥对不上，都是会直接报异常的。
     * @throws InvalidCipherTextException
     */
    public static byte[] decrypt(MySm2Engine.Mode mode, ECPrivateKeyParameters priKeyParameters, byte[] sm2Cipher)
            throws InvalidCipherTextException, IOException {
        MySm2Engine engine = new MySm2Engine(mode);
        engine.init(false, priKeyParameters);
        return engine.processBlock(sm2Cipher, 0, sm2Cipher.length);
    }

    public static ECDomainParameters getDomain() {
        // 获取一条SM2曲线参数
        X9ECParameters x9ECParameters = GMNamedCurves.getByName(SM2_CURVE_NAME);
        // 构造domain参数
        ECDomainParameters domain = new ECDomainParameters(x9ECParameters.getCurve(), x9ECParameters.getG(),
                x9ECParameters.getN(), x9ECParameters.getH());
        return domain;
    }

    /**
     * 私钥签名
     *
     * @param privateKey 私钥
     * @param content    待签名内容
     * @return
     */
    public static String sign(String privateKey, String content) throws CryptoException, CryptoException {
        // 待签名内容转为字节数组
        byte[] message = content.getBytes();

        BigInteger domainParameters = new BigInteger(1, Base64.getDecoder().decode(privateKey));
        ECDomainParameters domin = getDomain();

        // 私钥对象
        ECPrivateKeyParameters privateKeyParameters = new ECPrivateKeyParameters(domainParameters, domin);

        // 创建签名实例
        SM2Signer sm2Signer = new SM2Signer();

        // 初始化签名实例,带上ID,国密的要求,ID默认值:1234567812345678
        try {
            sm2Signer.init(true,
                    new ParametersWithID(
                            new ParametersWithRandom(privateKeyParameters, SecureRandom.getInstance("SHA1PRNG")),
                            Strings.toByteArray("1234567812345678")));
        } catch (NoSuchAlgorithmException e) {
            e.printStackTrace();
        }
        sm2Signer.update(message, 0, message.length);
        // 生成签名,签名分为两部分r和s,分别对应索引0和1的数组
        byte[] signBytes = sm2Signer.generateSignature();

        String sign = Base64.getEncoder().encodeToString(signBytes);

        return sign;
    }

    /**
     * 验证签名
     *
     * @param publicKey 公钥
     * @param content   待签名内容
     * @param sign      签名值
     * @return
     */
    public static boolean verify(String publicKey, String content, String sign) {
        // 待签名内容
        byte[] message = content.getBytes();
        byte[] signData = Base64.getDecoder().decode(sign);

        // 获取一条SM2曲线参数
        X9ECParameters sm2ECParameters = GMNamedCurves.getByName(SM2_CURVE_NAME);
        // 构造domain参数
        ECDomainParameters domainParameters = new ECDomainParameters(sm2ECParameters.getCurve(), sm2ECParameters.getG(),
                sm2ECParameters.getN());
        // 提取公钥点
        ECPoint pukPoint = sm2ECParameters.getCurve().decodePoint(java.util.Base64.getDecoder().decode(publicKey));
        // 公钥前面的02或者03表示是压缩公钥，04表示未压缩公钥, 04的时候，可以去掉前面的04
        ECPublicKeyParameters publicKeyParameters = new ECPublicKeyParameters(pukPoint, domainParameters);
        // 创建签名实例
        SM2Signer sm2Signer = new SM2Signer();
        ParametersWithID parametersWithID = new ParametersWithID(publicKeyParameters,
                Strings.toByteArray("1234567812345678"));
        sm2Signer.init(false, parametersWithID);
        sm2Signer.update(message, 0, message.length);
        // 验证签名结果
        boolean verify = sm2Signer.verifySignature(signData);
        return verify;
    }

    /**
     * 获取私钥（16进制字符串，头部不带00长度共64）
     * @param privateKey 私钥
     * @return
     */
    public static String getPriKeyHexString(PrivateKey privateKey){
        BCECPrivateKey s=(BCECPrivateKey)privateKey;
        String priKeyHexString = Hex.toHexString(s.getD().toByteArray());
        if(null!= priKeyHexString && priKeyHexString.length()==66 && "00".equals(priKeyHexString.substring(0,2))){
            return priKeyHexString.substring(2);
        }
        return priKeyHexString;
    }

    /**
     * 获取公钥（16进制字符串，头部带04长度共130）
     * @param publicKey
     * @return
     */
    public static String getPubKeyHexString(PublicKey publicKey){
        BCECPublicKey p=(BCECPublicKey)publicKey;
        return Hex.toHexString(p.getQ().getEncoded(false));
    }

}